#ifndef T_FORMATTER_HPP
#define T_FORMATTER_HPP

#define PLATFORM_SERVER

#include <string>
#include <vector>
#include <iomanip>
#include <sstream>
#include <cstdarg>

class TFormater {
public:
	struct StrLikeU8 {
		std::vector<uint8_t> content;
		StrLikeU8() {};
		StrLikeU8(const std::vector<uint8_t>& newContent);
		StrLikeU8(const std::string& newContent);
		size_t length();
		size_t find_first_of(char c, size_t offset = 0);
		size_t find_last_of(char c, size_t offset = 0);
		std::vector<uint8_t> substr(size_t offset, size_t count = std::string::npos);
		size_t find(const std::vector<uint8_t>& searchContent, int offset = 0);
		size_t find(const StrLikeU8& searchContent, int offset = 0);
		// only return 0 (match) or 1 for now
		size_t compare(const std::vector<uint8_t>& compareContent);

		//size_t find(std::vector<uint8_t>& main, std::vector<uint8_t>& sub, int offset);
	};

	TFormater(std::string strIn);
	int read(int size, std::string& strOut);
	int moveBy(int size);
	int parseFltBetween(std::string pre, std::string post, float& fltOut);

	static void indent(int num, std::string& content);
	static std::string indents(int num);
	static std::string cleanStr(std::string strIn);

	template<class Type>
	static std::string val2str(Type input);

	template<class Type>
	static std::string vals2str(void* ptr, int count);

	template<class Type>
	static std::string int2str(Type input, int digiNum = -1);

	template<class Type>
	static std::string int2HexStr(Type input, int byteNum);

	template<class Type>
	static std::string int2BitStr(Type input, int bitNum, bool seperateNegSign = false);

	template<class Type>
	static Type bitStr2int(std::string input);

	template<class Type>
	static Type str2Val(std::string strIn);

	template<class Type>
	static Type strHex2Val(std::string strIn);

	template<class Type0, class Type1>
	static std::string valPairs2str(
		std::vector<Type0> input0, std::vector<Type1> input1,
		char delimInner = ',', char delimSpl = ';');

	template<class Type0, class Type1>
	static void str2ValPairs(
		std::string strIn, std::vector<Type0>& output0, std::vector<Type1>& output1,
		char delimInner = ',', char delimSpl = ';');

	static std::string makeSpaces(int count);

	static bool isNumber(std::string input);

	static std::string char2str(char input);

	static std::string bool2str(bool input);

	static std::string int2str(int input, int digiNum = -1);
	static std::wstring int2wstr(int input, int digiNum = -1);
	static std::string ints2str(int* vals, int valNum, int digiNum = -1, char delim = ',');
	static std::string ints2str(std::vector<int>& vals, int digiNum = -1, char delim = ',');
	static std::string int2dArray2str(std::vector<std::vector<int>>& vals, int digiNum = -1);
	static std::string byte2hex(unsigned char val);
	static char byte2hexChar(unsigned char val);

	// before/after dots not implemented
	static std::string float2str(float input, int strLen = -1, int digitAfterDot = -1);
	static std::wstring float2wstr(float input, int strLen = -1, int digitAfterDot = -1);
	static std::string floats2str(float* vals, int valNum, int strLen = -1, int digitAfterDot = -1, char delim = ',');
	static std::string floats2str(std::vector<float>& vals, int strLen = -1, int digitAfterDot = -1, char delim = ',');
	static std::string float22str(float val0, float val1, int strLen = -1, int digitAfterDot = -1, char delim = ',');
	static std::string float32str(float val0, float val1, float val2, int strLen = -1, int digitAfterDot = -1, char delim = ',');
	static std::string float42str(float val0, float val1, float val2, float val3, int strLen = -1, int digitAfterDot = -1, char delim = ',');
	// variadic functions
	static std::string floats2str(int strLen, int digitAfterDot, char delim, int valNum, ...);

	static std::string double2str(double input);
	static std::string doubles2str(std::vector<double>& vals, int strLen = -1, int digitAfterDot = -1, char delim = ',');

	static std::string formatCurrentDateTimeToString();
	static std::string formatLaunchDateTimeToString();

	static int str2int(const char* strIn);
	static int str2int(std::string strIn);
	static int str2int(std::wstring strIn);
	static std::vector<int> str2ints(std::string strIn, char delim = ',');
	static int str2ints(std::string strIn, int* buffer, int maxNum, char delim = ',');
	static int u82int(uint8_t* dat, int len);

	static long long str2longlong(std::string strIn);
	static std::vector<long long> str2longlongs(std::string strIn, char delim = ',');

	static double str2double(const char* strIn);
	static double str2double(std::string strIn);
	static std::vector<double> str2doubles(std::string strIn, char delim = ',');
	static float str2float(const char* strIn);
	static float str2float(std::string strIn);
	static float str2float(std::wstring strIn);
	static std::vector<float> str2floats(std::string strIn, char delim = ',');
	static int str2floats(std::string strIn, float* dtOut, int dtLen, char delim = ',');
	static bool str2Bool(std::string strIn);
	static std::vector<uint8_t> str2u8(std::string strIn);
	static void str2u8(std::string strIn, std::vector<uint8_t>& u8VecInOut);
	static std::string str2DigitStr(std::string strIn);

	// if you want minus, just type +-
	static std::vector<std::string> breakByPlusSign(std::string strIn);

	//static std::string trimFloat(std::string floatStrIn, int digitNumAfterDot, bool suppressFormatWarning = true);

	// start with _ end with .
	static int parseImagenameToTime(std::string filename);

	static std::vector<std::string> split(std::string strIn, char delim);
	static std::vector<std::wstring> split(std::wstring strIn, wchar_t delim);
	static std::vector<std::string> splitVars(std::string strIn, char delim0 = '{', char delim1 = '}', std::vector<std::string>* interVarContentOut = nullptr);
	static std::vector<std::vector<uint8_t>> splitVars(std::vector<uint8_t> strIn, char delim0 = '{', char delim1 = '}', std::vector<std::vector<uint8_t>>* interVarContentOut = nullptr);
	static int split(std::wstring strIn, std::wstring* strsOut, int maxNum, wchar_t delim);

	static std::string getSubstrInBetween(std::string strIn, char from, char to);
	static std::string expandStrToSize(std::string strIn, int newSize);

	static bool beginWith(std::wstring mainStr, std::wstring subString);
	static bool beginWith(std::string mainStr, std::string subString);
	static bool beginWith(std::vector<uint8_t> mainStr, std::string subString);

	// some reserved characters may not be printable
	static std::string toDisplayable(std::string data, char delim = '\0');
	static std::string toDisplayable(std::vector<uint8_t> data, char delim = '\0');
	static std::string toDisplayable(void* data, int len, char delim = '\0');

	static std::string clockT2str(clock_t timeIn, std::string delim, bool withMs = false);
	static long str2Seconds(std::wstring timeFormated);
	static long str2Seconds(std::string timeFormated);

	static std::wstring string2wstring(std::string strIn);
	static std::string wstring2string(std::wstring strIn);

	static std::string removeEoL(std::string strIn);

	// Auxi::formatImgFilename
	static std::string formatFilenameWithIdx(std::string filenameFormat, int imgIdx);

	static int matchTemplates(const std::vector<uint8_t>& dataIn, const std::vector<std::vector<uint8_t>>& templates);

private:
	StrLikeU8 buffer;
	int offset;
};

class TWFormater {
public:
	template<class Type>
	static std::wstring val2str(Type input);

	template<class Type>
	static std::wstring vals2str(void* ptr, int count);

	template<class Type>
	static std::wstring int2str(Type input, int digiNum = -1);

	template<class Type>
	static std::wstring int2HexStr(Type input, int byteNum);

	template<class Type>
	static std::wstring int2BitStr(Type input, int bitNum = -1);

	template<class Type>
	static Type bitStr2int(std::wstring input);

	template<class Type>
	static Type str2Val(std::wstring strIn);

	template<class Type>
	static Type strHex2Val(std::wstring strIn);

	static std::wstring int2str(int input, int digiNum = -1);
	static std::wstring ints2str(int* vals, int valNum, int digiNum = -1, wchar_t delim = L',');
	static std::wstring ints2str(std::vector<std::vector<int>> vals, int digiNum = -1);

	static std::wstring float2str(float input, int strLen = -1, int digitAfterDot = -1);
	static std::wstring floats2str(float* vals, int valNum, int strLen = -1, int digitAfterDot = -1, wchar_t delim = L',');
	static std::wstring float22str(float val0, float val1, int strLen = -1, int digitAfterDot = -1, wchar_t delim = L',');
	static std::wstring float32str(float val0, float val1, float val2, int strLen = -1, int digitAfterDot = -1, wchar_t delim = L',');
	static std::wstring floats2str(std::vector<std::vector<float>> vals, int digiNum = -1);

	static std::wstring formatCurrentDateTimeToString();
	static std::wstring dateTime2FullStr(clock_t duration);
	static std::wstring dateTime2FullStr(int hours, int minutes, int seconds);
	static std::wstring dateTime2ShortStr(int hours, int minutes, int seconds);

	static bool charIsElementaryFormula(wchar_t ch);
	static bool strIsNumber(std::wstring strIn);
	static float str2float(std::wstring strIn);
	static double str2double(std::wstring strIn);
	static float formula2float(std::wstring strIn);
	static int str2int(const wchar_t* strIn);
	static int str2int(std::wstring strIn);
	static int str2ints(std::wstring strIn, int* buffer, int maxNum, char delim = ',');
	static std::vector<int> str2ints(std::wstring strIn, char delim = ',', std::wstring removeChars = L"");
	static std::vector<float> str2floats(std::wstring strIn, char delim = ',');
	static std::vector<uint8_t> str2u8(std::wstring strIn);

	static std::vector<std::vector<int>> str2intVecs(std::wstring strIn);
	static std::vector<std::vector<float>> str2floatVecs(std::wstring strIn);

	static bool beginWith(std::wstring mainStr, std::wstring subString, std::wstring* followingPartOut = nullptr);
	static bool endWith(std::wstring mainStr, std::wstring subString, std::wstring* leadingPartOut = nullptr);
	static bool between(std::wstring& mainStr, std::wstring start, std::wstring end, std::wstring& contentOut);

	static std::wstring combine(std::wstring* strIn, int number, wchar_t delim = L',');
	static std::wstring combine(std::wstring* strIn, int number, std::wstring delim);

	static std::vector<std::wstring> split(std::wstring strIn, wchar_t delim);
	static void append(std::wstring& strLong, std::wstring& strApp, std::wstring delim);
	// A+b-C
	static std::vector<std::wstring> splitPlusMinus(std::wstring strIn);
	static int split(std::wstring strIn, std::wstring* strsOut, int maxNum, wchar_t delim);
	static std::vector<std::wstring> breakByPlusSign(std::wstring strIn);

	static std::wstring makeSpaces(int count);
	static std::wstring addJsonSlashes(std::wstring);

	static std::wstring removeDelims(const std::wstring src, const std::wstring delims);

	static std::wstring toLower(std::wstring strIn);
};

class TStrTools {
public:
	static void subVectorOfStrings(std::vector<std::string>& stringsIn, std::vector<std::string>& stringsOut, int startIdx, int endIdx);
	static bool compareCharBuffers(void* buf0, int buf0Len, void* buf1, int but1Len);

	static bool endWith(const std::string& strFull, const std::string& strShort);

	static std::string splitFuncAndParas(std::string funcNameAndParas, std::vector<std::string>* parasOut);

	static bool parseStrForVars(std::string reply, std::string keyword,
		int* followedByVal0Out = nullptr,
		int* followedByVal1Out = nullptr,
		int* followedByVal2Out = nullptr);
	static bool parseStrForVarVec(std::string reply, std::string keyword,
		std::vector<int>& followedByValsOut);
};

class TConvByte {
private:
	uint8_t* byteSrcOrigin;
	uint8_t* byteSrcRec;
	size_t totalLen;
public:
	size_t bytesProcessed;
	size_t localCounter;
	TConvByte(uint8_t* byteSrc, size_t totalLenIn = 0xFFFFFFFF);
	TConvByte(std::vector<uint8_t>& byteStreamAllocated);
	//void fromByte(int &dataDst, uint8_t** byteSrc);
	//void fromByte(float &dataDst, uint8_t** byteSrc);
	void fromByte(int& dataDst);
	void fromByte(int* dataPtr, int count);
	void fromByte(float& dataDst);
	void fromByte(float* dataPtr, int count);
	void fromByte(double& dataDst);
	void fromByte(char& dataDst);
	void fromByte(uint8_t& dataDst);
	void fromByte(size_t& dataDst);
	void fromByte(int64_t& dataDst);
	void fromByte(std::vector<int>& dataDst);
	void fromByteCopy(void* dstPtr, size_t count);
	void fromByte(unsigned short& dataDst);
	void fromByte(short& dataDst);
	uint8_t readIDT();
	int readInt();
	short readShort();
	int64_t readInt64();
	float readFloat();
	size_t readSizeT();
	bool toByte(short dataDst, int offsetFromStart = -1);
	bool toByte(int dataDst, int offsetFromStart = -1);
	bool toByte(int* dataPtr, int count);
	bool toByte(int64_t dataDst, int offsetFromStart = -1);
	bool toByte(float dataDst);
	bool toByte(float* dataPtr, int count);
	bool toByte(double dataDst);
	bool toByte(double* dataPtr, int count);
	bool toByte(char dataDst);
	bool toByte(uint8_t dataDst);
	bool toByte(std::vector<int>& dataSrc);
	bool toByteCopy(void* srcPtr, size_t count);
	bool localCounterReset();
	bool skipBytes(int count);
	// try, without moving
	uint8_t tryIDTFlag();
	bool confirmIDTFlag(uint8_t expectedFlag);

	uint8_t* getRef();

private:
	template<class Type>
	bool toByteCore(Type dataSrc, int offsetFromStart = -1) {
		if (offsetFromStart >= 0) {
			if (offsetFromStart + sizeof(Type) > totalLen) {
				return false;
			}
			else {
				memcpy(byteSrcOrigin + offsetFromStart, &dataSrc, sizeof(Type));
				return true;
			}
		}
		else {
			if (bytesProcessed + sizeof(Type) > totalLen) {
				return false;
			}
			else {
				memcpy(byteSrcRec, &dataSrc, sizeof(Type));
				byteSrcRec += sizeof(Type);
				bytesProcessed += sizeof(Type);
				localCounter += sizeof(Type);
				return true;
			}
		}
	};
	template<class Type>
	void fromByteCore(Type& dataDst) {
		memcpy(&dataDst, byteSrcRec, sizeof(Type));
		byteSrcRec += sizeof(Type);
		bytesProcessed += sizeof(Type);
		localCounter += sizeof(Type);
	};
	template<class Type>
	bool toByteCore(Type* dataSrc, int count) {
		size_t byteLen = count * sizeof(Type);
		if (bytesProcessed + byteLen > totalLen) {
			return false;
		}
		else {
			memcpy(byteSrcRec, dataSrc, byteLen);
			byteSrcRec += byteLen;
			bytesProcessed += byteLen;
			localCounter += byteLen;
			return true;
		}
	};
	template<class Type>
	void fromByteCore(Type* dataDst, int count) {
		size_t byteLen = count * sizeof(Type);
		memcpy(dataDst, byteSrcRec, byteLen);
		byteSrcRec += byteLen;
		bytesProcessed += byteLen;
		localCounter += byteLen;
	};
};

class ProcS {
public:
	template<class Type>
	static bool setIfNotNullPtr(const Type& val, Type* ref);
	template<class Type>
	static bool setIfNotNullPtr(Type* val, Type& ref);

	static bool setIntIfNotNullPtr(int val, int* ref);
	static bool setFltIfNotNullPtr(float val, float* ref);
	static bool setDbIfNotNullPtr(double val, double* ref);
	static bool setBoolIfNotNullPtr(bool val, bool* ref);

	template<class Type>
	static std::vector<Type> flatten(std::vector < std::vector<Type>> matYX);

	template<class Type>
	static bool pushBackIfNotNullPtr(Type val, std::vector <Type>* ref);

	template<class Type>
	static int indexOf(std::vector<Type>& vals, Type val);
};

template<class Type>
static std::string TFormater::val2str(Type input) {
#ifdef PLATFORM_SERVER
	std::stringstream ss;
	ss << input;
	return ss.str();
#else
	return "";
#endif
}

template<class Type>
static std::wstring TWFormater::val2str(Type input) {
#ifdef PLATFORM_SERVER
	std::wstringstream ss;
	ss << input;
	return ss.str();
#else
	return "";
#endif
}

template<class Type>
static std::string TFormater::vals2str(void* ptr, int count) {
#ifdef PLATFORM_SERVER
	Type* input = (Type*)ptr;
	std::stringstream ss;
	ss << "[";
	for (int idx = 0; idx < count - 1; idx++) {
		ss << *input++;
		ss << ",";
	}
	ss << *input;
	ss << "]";
	return ss.str();
#else
	return "";
#endif
}

template<class Type>
static std::wstring TWFormater::vals2str(void* ptr, int count) {
#ifdef PLATFORM_SERVER
	Type* input = (Type*)ptr;
	std::wstringstream ss;
	ss << "[";
	for (int idx = 0; idx < count - 1; idx++) {
		ss << *input++;
		ss << ",";
	}
	ss << *input;
	ss << "]";
	return ss.str();
#else
	return "";
#endif
}

template<class Type>
static std::string TFormater::int2str(Type input, int digiNum) {
#ifdef PLATFORM_SERVER
	if (digiNum <= 0) {
		std::stringstream ss;
		ss << input;
		return ss.str();
	}
	else {
		std::ostringstream oss;
		oss << std::setfill('0') << std::setw(digiNum) << input;
		return oss.str();
	}
#else
	return "";
#endif
}

template<class Type>
static std::wstring TWFormater::int2str(Type input, int digiNum) {
#ifdef PLATFORM_SERVER
	if (digiNum <= 0) {
		std::wstringstream ss;
		ss << input;
		return ss.str();
	}
	else {
		std::wostringstream oss;
		oss << std::setfill(L'0') << std::setw(digiNum) << input;
		return oss.str();
	}
#else
	return "";
#endif
}

template<class Type>
static std::string TFormater::int2HexStr(Type input, int byteNum) {
	std::string ret = "";
	for (int byteIdx = 0; byteIdx < byteNum; byteIdx++) {
		unsigned char byte = input & 0xff;
		ret = byte2hex(byte) + ret;
		input = input >> 8;
	}
	return ret;
}

template<class Type>
static std::wstring TWFormater::int2HexStr(Type input, int byteNum) {
	std::wstring ret = "";
	for (int byteIdx = 0; byteIdx < byteNum; byteIdx++) {
		unsigned char byte = input & 0xff;
		ret = byte2hex(byte) + ret;
		input = input >> 8;
	}
	return ret;
}

template<class Type>
static std::string TFormater::int2BitStr(Type input, int bitNum, bool seperateNegSign) {
	std::string ret = "";
	if (seperateNegSign) {
		if (input < 0) {
			ret = "-";
			input = -input;
		}
	}
	for (int bitIdx = 0; bitIdx < bitNum; bitIdx++) {
		bool bit = input & 0x01;
		ret = (bit ? "1" : "0") + ret;
		input = input >> 1;
	}
	return ret;
}

template<class Type>
static std::wstring TWFormater::int2BitStr(Type input, int bitNum) {
	if (bitNum < 0) {
		Type inputCp = input;
		for (bitNum = 0; bitNum < sizeof(Type) * 8; bitNum++) {
			if (inputCp == 0) {
				break;
			}
			else {
				inputCp = (inputCp >> 1);
			}
		}
	}

	std::wstring ret = L"";
	for (int bitIdx = 0; bitIdx < bitNum; bitIdx++) {
		bool bit = input & 0x01;
		ret = (bit ? L"1" : L"0") + ret;
		input = input >> 1;
	}
	return ret;
}

template<class Type>
static Type TFormater::bitStr2int(std::string input) {
	Type ret = 0;
	if (input.length() == 0) {
		return ret;
	}
	bool isNeg = (input[0] == '-');
	if (isNeg) {
		input = input.substr(1);
	}
	for (char c : input) {
		if (c != '0') {
			ret = (ret << 1) | 0b1;
		}
		else {
			ret = (ret << 1);
		}
	}
	return ret;
}

template<class Type>
static Type TWFormater::bitStr2int(std::wstring input) {
	Type ret = 0;
	for (wchar_t c : input) {
		if (c != '0') {
			ret = (ret << 1) | 0b1;
		}
		else {
			ret = (ret << 1);
		}
	}
	return ret;
}

template<class Type>
static Type TFormater::str2Val(std::string strIn) {
	if (strIn.find('.') == std::string::npos) {
		return str2int(strIn);
	}
	else {
		return str2double(strIn);
	}
}

template<class Type>
static Type TWFormater::str2Val(std::wstring strIn) {
	if (strIn.find('.') == std::wstring::npos) {
		return str2int(strIn);
	}
	else {
		return str2double(strIn);
	}
}

template<class Type>
static Type TFormater::strHex2Val(std::string strIn) {
	Type val = 0x00;
	for (int idx = 2; idx < strIn.length(); idx++) {
		val = val << 4;
		char c = strIn.at(idx);
		if (c >= '0' && c <= '9') {
			c = c - '0';
		}
		else if (c >= 'A' && c <= 'F') {
			c = c - 'A' + 10;
		}
		else if (c >= 'a' && c <= 'f') {
			c = c - 'a' + 10;
		}
		val |= c;
	}
	return val;
}

template<class Type>
static Type TWFormater::strHex2Val(std::wstring strIn) {
	Type val = 0x00;
	for (int idx = 2; idx < strIn.length(); idx++) {
		val = val << 4;
		wchar_t c = strIn.at(idx);
		if (c >= L'0' && c <= L'9') {
			c = c - L'0';
		}
		else if (c >= L'A' && c <= L'F') {
			c = c - L'A' + 10;
		}
		else if (c >= L'a' && c <= L'f') {
			c = c - L'a' + 10;
		}
		val |= c;
	}
	return val;
}

template<class Type0, class Type1>
static std::string TFormater::valPairs2str(
	std::vector<Type0> input0, std::vector<Type1> input1,
	char delimInner, char delimSpl) {
#ifdef PLATFORM_SERVER
	if (input0.size() != input1.size() || input0.size() == 0) {
		return "";
	}
	std::stringstream ss;
	for (int idx = 0; idx < input0.size(); idx++) {
		ss << input0[idx] << delimInner << input1[idx] << delimSpl;
	}
	return ss.str();
#else
	return "";
#endif
}

template<class Type0, class Type1>
static void TFormater::str2ValPairs(
	std::string strIn, std::vector<Type0>& output0, std::vector<Type1>& output1,
	char delimInner, char delimSpl) {
#ifdef PLATFORM_SERVER
	output0.clear();
	output1.clear();
	std::vector<std::string> valPairStrs = split(strIn, delimSpl);
	for (std::string& valPairStr : valPairStrs) {
		if (valPairStr.length() > 0) {
			std::vector<std::string> vals = split(valPairStr, delimInner);
			output0.push_back(str2Val<Type0>(vals[0]));
			output1.push_back(str2Val<Type1>(vals[1]));
		}
	}
#else
	return "";
#endif
}

template<class Type>
bool ProcS::setIfNotNullPtr(const Type& val, Type* ref) {
	if (ref == nullptr) {
		return false;
	}
	else {
		*ref = val;
		return true;
	}
}

template<class Type>
bool ProcS::setIfNotNullPtr(Type* val, Type& ref) {
	if (val == nullptr) {
		return false;
	}
	else {
		ref = *val;
		return true;
	}
}

template<class Type>
std::vector<Type> ProcS::flatten(std::vector < std::vector<Type>> matYX) {
	std::vector<Type> vec;
	for (std::vector<Type>& row : matYX) {
		for (Type& ele : row) {
			vec.push_back(ele);
		}
	}
	return vec;
}

template<class Type>
bool ProcS::pushBackIfNotNullPtr(Type val, std::vector <Type>* ref) {
	if (ref == nullptr) {
		return false;
	}
	else {
		ref->push_back(val);
		return true;
	}
}

template<class Type>
int ProcS::indexOf(std::vector<Type>& vals, Type val) {
	for (int idx = 0; idx < vals.size(); idx++) {
		if (vals[idx] == val) {
			return idx;
		}
	}
	return -1;
}

#endif